package pr1;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import pr1.cromosoma.Cromosoma;
import pr1.cromosoma.CromosomaFactory;
import pr1.cruce.CruceSimple;
import pr1.cruce.EstrategiaCruce;
import pr1.seleccion.EstrategiaSeleccion;
import pr1.seleccion.Ruleta;

public class AlgoritmoGenetico<E> {

    public static final double EPSILON = 0.0001;

    private final boolean maximizar;
    private int numElite;
    private final ArrayList<Cromosoma<E>> elite;
	private ArrayList<Cromosoma<E>> poblacion;
	private EstrategiaCruce estrategiaCruce;
	private EstrategiaSeleccion estrategiaSeleccion;
	private ArrayList<Cromosoma<E>> hijos;
	private final ArrayList<Cromosoma<E>> seleccionados;
	private double probCruce;
	private double probMutacion;
	private Cromosoma<E> mejor;
    private double mejorAptitud;
	private CromosomaFactory factory;
	private Comparator comparator;

	public AlgoritmoGenetico(int tamPoblacion, int elitismo, boolean maximizar, CromosomaFactory factory) {

        this.maximizar = maximizar;
        this.numElite = elitismo;

        if(maximizar) { //orden inverso => el primero el mayor
            comparator = new Comparator<Cromosoma<E>>() {

                public int compare(Cromosoma<E> o1, Cromosoma<E> o2) {
                    return -Double.compare(o1.getAptitud(), o2.getAptitud());
                }

            };
            mejorAptitud = Double.MIN_VALUE;
        }
        else { //orden natural => el primero el menor
            comparator = new Comparator<Cromosoma<E>>() {

                public int compare(Cromosoma<E> o1, Cromosoma<E> o2) {
                    return Double.compare(o1.getAptitud(), o2.getAptitud());
                }

            };

            mejorAptitud = Double.MAX_VALUE;
        }

		this.factory = factory;
		this.estrategiaCruce = new CruceSimple(factory);
		this.estrategiaSeleccion = new Ruleta();

        mejor = factory.generaCromosoma();

        elite           = new ArrayList<Cromosoma<E>>(elitismo);
        poblacion       = new ArrayList<Cromosoma<E>>(tamPoblacion + elitismo);
		seleccionados   = new ArrayList<Cromosoma<E>>(tamPoblacion);
		hijos           = new ArrayList<Cromosoma<E>>(tamPoblacion + elitismo);
 
		for (int i = 0; i < tamPoblacion; i++) {
			poblacion.add(factory.generaCromosoma());
            seleccionados.add(factory.generaCromosoma());
            hijos.add(factory.generaCromosoma());
		}

		evalua();
	}

	public ArrayList<Cromosoma<E>> getPoblacion() {
		return poblacion;
	}

	public EstrategiaCruce getCruce() {
		return estrategiaCruce;
	}

	public void setCruce(EstrategiaCruce cruce) {
		this.estrategiaCruce = cruce;
	}

	public EstrategiaSeleccion getSeleccion() {
		return estrategiaSeleccion;
	}

	public void setSeleccion(EstrategiaSeleccion seleccion) {
		this.estrategiaSeleccion = seleccion;
	}

	public ArrayList<Cromosoma<E>> getHijos() {
		return hijos;
	}

	public ArrayList<Cromosoma<E>> getSeleccionados() {
		return seleccionados;
	}

	public double getProbCruce() {
		return probCruce;
	}

	public void setProbCruce(double probCruce) {
		this.probCruce = probCruce;
	}

	public double getProbMutacion() {
		return probMutacion;
	}

	public void setProbMutacion(double probMutacion) {
		this.probMutacion = probMutacion;
	}

	public Cromosoma<E> getMejor() {
		return mejor;
	}

	public void setMejor(Cromosoma<E> mejor) {
		this.mejor = mejor;
	}

    public Cromosoma<E> getMejorActual() {
        return poblacion.get(0);
    }
    
	public Comparator getComparator() {
		return comparator;
	}

	public void setComparator(Comparator comparator) {
		this.comparator = comparator;
	}

	public final void evalua() {

		Cromosoma<E> maxLocal = null;
        Cromosoma<E> minLocal = null;
		double maxAptLocal = Double.MIN_VALUE;
        double minAptLocal = Double.MAX_VALUE;
        double sumaptitud = 0;

		for (Cromosoma<E> c : poblacion) {
            double apt = c.evalua();
            c.setAptitud(apt);
            sumaptitud += apt;
            if (apt > maxAptLocal) {
                maxLocal = c;
                maxAptLocal = apt;
            }
            if (apt < minAptLocal) {
                minLocal = c;
                minAptLocal = apt;
            }
		}

        double origen = 0;
        int signo;
        int size = poblacion.size();
        if(maximizar) {
            origen = minAptLocal;
            sumaptitud -= minAptLocal * size;
            signo = 1;
        }
        else {
            origen = maxAptLocal;
            sumaptitud -= maxAptLocal * size;
            sumaptitud = -sumaptitud;
            signo = -1;
        }
        if(Math.abs(sumaptitud) < EPSILON) { //max y min iguales
            sumaptitud = maxAptLocal * size;
            origen = 0;
            signo = 1;
        }
        double puntAcum = 0;
		for (Cromosoma<E> c : poblacion) {
            double puntuacion = signo * (c.getAptitud() - origen) / sumaptitud;
            assert (puntuacion >= 0 - EPSILON/2 && puntuacion <= 1 + EPSILON/2) : "La puntuacion '" + puntuacion + "'es invalida";
            puntAcum += puntuacion;
			c.setPuntuacion(puntuacion);
		}
        assert(puntAcum > 0.9 && puntAcum < 1.1) : "La probabilidad no suma 1 sino "+puntAcum;

        if(maximizar) {
            if (maxAptLocal > mejorAptitud) {
                mejor.copia(maxLocal);
                mejorAptitud = maxAptLocal;
            }
        }
        else {
            if (minAptLocal < mejorAptitud) {
                mejor.copia(minLocal);
                mejorAptitud = minAptLocal;
            }
        }
        Collections.sort(poblacion, comparator);
  	}

	public void selecciona() {
        elite.clear();
        for(int i = 0; i < numElite; i++) {
            elite.add(poblacion.get(i).clone());
        }
		estrategiaSeleccion.seleccion(poblacion, seleccionados, this);
	}

	public void cruza() {
		estrategiaCruce.cruce(seleccionados, probCruce, hijos);
	}

	public void muta() {
		for (Cromosoma<E> c : hijos) {
            c.muta(probMutacion);
		}
	}

	public void reemplaza() {
        ArrayList<Cromosoma<E>> aux = hijos;
        hijos = poblacion;
        poblacion = aux;

        poblacion.addAll(elite);
	}

	public void nuevaIteracion() {
        int size = poblacion.size();
		selecciona();
		cruza();
		muta();
		reemplaza();
		evalua();
        for(int i = 0; i < numElite; i++) {
            poblacion.remove(size - i -1);
        }
        assert(size == poblacion.size());
	}

}
